#include "CogMain.h"
#include "CogEvolver.h"

// ------------------------------------------------------------------------------------------

void cogPrintHowto()
{
	cout << endl;
	cout << resetiosflags(ios::fixed) << setprecision(2) << setw(5);
	cout << "--------- {Co}ncurrent {G}rammar {E}xploration a{n}d {T}ransformation ---------" << endl;
	cout << "-------------------------------------------------------------------------------" << endl;
	cout << "Usage: cogent -i[pct] infile -o[pctrg] outfile -s[pctrg] -@pname pvalue" << endl;
	cout << "Valid command-line options:" << endl;
	cout << "	-i	Specifies an input file- must be followed by \"infile\"" << endl;
	cout << "	infile	Name of the input file" << endl;
	cout << "	-o	Specifies an output file- must be followed by \"outfile\"" << endl;
	cout << "	outfile	Name of the output file" << endl;
	cout << "	-s	Print outputs to stdout";
	cout << "	[...]	Specifies in/out-file types" << endl;
	cout << "		must be in square brackets, can be combined (default: all)" << endl;
	cout << "	[p]	Parameter file type" << endl;
	cout << "	[c]	Candidate file type (contains instantiated rules)" << endl;
	cout << "	[t]	Template file type (contains template grammar(s))" << endl;
	cout << "	[r]	Result file type (contains solution statistics)" << endl;
	cout << "	[g]	Generation file type (contains generation statistics)" << endl;
	cout << "	-@	Specifies a parameter override (multiple possible)" << endl;
	cout << "		must be followed by parameter name and value" << endl;
	cout << "	pname	Name of parameter" << endl;
	cout << "	pvalue	Value of parameter" << endl;
	cout << "----------------------------------------------------- | Martin Luerssen | 2010+" << endl;
}

// -----

//int _tmain(int argc, _TCHAR* argv[])
int main(int argc, char* argv[])
{
	// print usage info when no command-line arguments
	if (argc < 2) 
	{
		cogPrintHowto();
		return 1;
	}
	// immediately print anything to stdout that enters the message log
	globalMessageLog.setInstantDump(true);
	// process arguments
	list<CogArgument> argList;
	int readyForMoreArgs = 0;
	for (int i = 1; i < argc; i++)
	{
		if (readyForMoreArgs)
		{
			argList.back().valueList.push_back(argv[i]);
			readyForMoreArgs--;
		}
		else
		if (argv[i][0] == '-')
		{
			CogArgumentType type = COGARG_NONE;
			int j = 1;
			while (argv[i][j] == '-') { j++; } // ignore further -'s
			switch (argv[i][j])
			{
			case 'i':
			case 'I':
				type = COGARG_FILE_INPUT;
				readyForMoreArgs = 1;
				break;
			case 'o':
			case 'O':
				type = COGARG_FILE_OUTPUT;
				readyForMoreArgs = 1;
				break;
			case 's':
			case 'S':
				type = COGARG_STDOUT;
				break;
			case '@':
				{
					argList.push_back(CogArgument(COGARG_PARAMETER));
					string paramName;
					for (size_t k = j+1; k < strlen(argv[i]); k++)
					{
						paramName.push_back(argv[i][k]);
					}
					argList.back().valueList.push_back(paramName);
					readyForMoreArgs = 1;
				}
				break;
			default:
				cout << "Unknown command-line argument: " << argv[i] << endl;
				cogPrintHowto();
				return 2;
			}
			if (type & (COGARG_FILE_INPUT | COGARG_FILE_OUTPUT | COGARG_STDOUT))
			{
				j++;
				if (argv[i][j] == '[')
				{	
					for (++j; argv[i][j] != ']'; j++)
					{
						switch (argv[i][j])
						{
						case 'p':
						case 'P':
							type |= COGARG_OUTPUT_PARAMETERS;
							break;
						case 'c':
						case 'C':
							type |= COGARG_OUTPUT_CANDIDATES;
							break;
						case 't':
						case 'T':
							type |= COGARG_OUTPUT_TEMPLATES;
							break;
						case 'r':
						case 'R':
							type |= COGARG_OUTPUT_RESULTS;
							break;
						case 'g':
						case 'G':
							type |= COGARG_OUTPUT_GENERATIONS;
							break;
						default:
							cout << "Unknown command-line file type: " << argv[i][j] << endl;
							cogPrintHowto();
							return 2;
						}
					}
				}
				else if (!argv[i][j])
				{
					type |= COGARG_OUTPUT_ALLTYPES;
				}
				else
				{
					cout << "Invalid command-line argument (missing [): " << argv[i] << endl;
					cogPrintHowto();
					return 2;
				}
				argList.push_back(CogArgument(type));
			}
		}
		else
		{
			cout << "Invalid command-line argument (missing -): " << argv[i] << endl;
			cogPrintHowto();
			return 2;
		}
	}
	if (readyForMoreArgs)
	{
		cout << "Incomplete command-line: expecting " << readyForMoreArgs << " more argument" << ((readyForMoreArgs > 1) ? "s " : " ") << endl;
		cogPrintHowto();
		return 2;
	}

	// :::::::::::::::

	CogEvolver evolver;
	
	// read parameters
	for (list<CogArgument>::iterator arg_iter = argList.begin(); arg_iter != argList.end(); ++arg_iter)
	{
		if (arg_iter->type & COGARG_PARAMETER)
		{
			if (!evolver.parameters.assign(arg_iter->valueList.front(), arg_iter->valueList.back()))
			{
				return 2;
			}
		}
		else 
		{
			if (arg_iter->type & COGARG_OUTPUT_GENERATIONS) // compute & store generational statistics if written out later
			{
				evolver.recordStats = true;
			}
			if ((arg_iter->type & COGARG_FILE_INPUT) && (arg_iter->type & COGARG_OUTPUT_PARAMETERS))
			{
				filebuf fb;
				if (!fb.open(arg_iter->valueList.back().c_str(), ios::in))
				{
					cout << "Cannot read parameters from input file: " << arg_iter->valueList.back() << endl;
					return 3;
				}
				istream is(&fb);
				if (!evolver.parameters.fromStream(is))
				{
					cout << "Error while parsing parameter from input file: " << arg_iter->valueList.back().c_str() << endl;
					fb.close();
					return 3;
				}
				fb.close();
			}
		}
	}

	// interpret evolutionary parameters
	evolver.initialize();
	
	// read grammar files
	for (list<CogArgument>::iterator arg_iter = argList.begin(); arg_iter != argList.end(); ++arg_iter)
	{
		if ((arg_iter->type & COGARG_FILE_INPUT) && ((arg_iter->type & COGARG_OUTPUT_CANDIDATES) || (arg_iter->type & COGARG_OUTPUT_TEMPLATES)))
		{
			filebuf fb;
			if (!fb.open(arg_iter->valueList.back().c_str(), ios::in))
			{
				cout << "Cannot read grammar from input file: " << arg_iter->valueList.back() << endl;
				return 4;
			}
			istream is(&fb);
			if (!evolver.grammar.fromStream(is, arg_iter->type))
			{
				cout << "Error while parsing grammar from input file: " << arg_iter->valueList.back().c_str() << endl;
				fb.close();
				return 4;
			}
			fb.close();
		}
	}
	// no starting templates at all?
	if (!evolver.grammar.startingTemplates.size())
	{
		cout << "Cannot proceed: no starting template productions loaded" << endl;
		return 4;
	}

	// :::::::::::::::

	// evaluate existing solutions (loaded from file)
	evolver.evaluateSolutions();
	// evolve population
	evolver.evolve();
	
	// :::::::::::::::

	// write parameters
	for (list<CogArgument>::iterator arg_iter = argList.begin(); arg_iter != argList.end(); ++arg_iter)
	{
		if ((arg_iter->type & COGARG_FILE_OUTPUT) && (arg_iter->type & COGARG_OUTPUT_PARAMETERS))
		{
			filebuf fb;
			if (!fb.open(arg_iter->valueList.back().c_str(), ios::out | ios::trunc))
			{
				cout << "Cannot write parameters to output file: " << arg_iter->valueList.back() << endl;
				return 5;
			}
			ostream os(&fb);
			evolver.parameters.toStream(os);
			fb.close();
		}
		else if ((arg_iter->type & COGARG_STDOUT) && (arg_iter->type & COGARG_OUTPUT_PARAMETERS))
		{
			evolver.parameters.toStream(cout);
		}
	}

	// write grammar files
	for (list<CogArgument>::iterator arg_iter = argList.begin(); arg_iter != argList.end(); ++arg_iter)
	{
		if ((arg_iter->type & COGARG_FILE_OUTPUT) && ((arg_iter->type & COGARG_OUTPUT_CANDIDATES) || (arg_iter->type & COGARG_OUTPUT_TEMPLATES)))
		{
			filebuf fb;
			if (!fb.open(arg_iter->valueList.back().c_str(), ios::out | ((arg_iter->type & COGARG_OUTPUT_PARAMETERS) ? ios::app : ios::trunc)))
			{
				cout << "Cannot write grammar to output file: " << arg_iter->valueList.back() << endl;
				return 5;
			}
			ostream os(&fb);
			evolver.grammar.toStream(os, arg_iter->type);
			fb.close();
		}
		else if ((arg_iter->type & COGARG_STDOUT) && ((arg_iter->type & COGARG_OUTPUT_CANDIDATES) || (arg_iter->type & COGARG_OUTPUT_TEMPLATES)))
		{
			evolver.grammar.toStream(cout, arg_iter->type);
		}
	}

	// write solution results
	for (list<CogArgument>::iterator arg_iter = argList.begin(); arg_iter != argList.end(); ++arg_iter)
	{
		if ((arg_iter->type & COGARG_FILE_OUTPUT) && (arg_iter->type & COGARG_OUTPUT_RESULTS))
		{
			filebuf fb;
			if (!fb.open(arg_iter->valueList.back().c_str(), ios::out | (((arg_iter->type & COGARG_OUTPUT_PARAMETERS) || (arg_iter->type & COGARG_OUTPUT_CANDIDATES) || (arg_iter->type & COGARG_OUTPUT_TEMPLATES)) ? ios::app : ios::trunc)))
			{
				cout << "Cannot write solution results to output file: " << arg_iter->valueList.back() << endl;
				return 5;
			}
			ostream os(&fb);
			evolver.solutionsToStream(os);
			fb.close();
		}
		else if ((arg_iter->type & COGARG_STDOUT) && (arg_iter->type & COGARG_OUTPUT_RESULTS))
		{
			evolver.solutionsToStream(cout);
		}
	}

	// write generational stats
	for (list<CogArgument>::iterator arg_iter = argList.begin(); arg_iter != argList.end(); ++arg_iter)
	{
		if ((arg_iter->type & COGARG_FILE_OUTPUT) && (arg_iter->type & COGARG_OUTPUT_GENERATIONS))
		{
			filebuf fb;
			if (!fb.open(arg_iter->valueList.back().c_str(), ios::out | (((arg_iter->type & COGARG_OUTPUT_PARAMETERS) || (arg_iter->type & COGARG_OUTPUT_CANDIDATES) || (arg_iter->type & COGARG_OUTPUT_TEMPLATES) || (arg_iter->type & COGARG_OUTPUT_CANDIDATES) || (arg_iter->type & COGARG_OUTPUT_RESULTS)) ? ios::app : ios::trunc)))
			{
				cout << "Cannot write generational statistics to output file: " << arg_iter->valueList.back() << endl;
				return 5;
			}
			ostream os(&fb);
			evolver.statsToStream(os);
			fb.close();
		}
		else if ((arg_iter->type & COGARG_STDOUT) && (arg_iter->type & COGARG_OUTPUT_GENERATIONS))
		{
			evolver.statsToStream(cout);
		}
	}

	// exit program
    return 0;
}

// ------------------------------------------------------------------------------------------